﻿using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using HighDiveAdmin.Handlers;
using HighDiveEngine.Aggregates;
using HighDiveEngine.Model;
using HighDiveEngine.Resources;

namespace HighDiveAdmin.Engine
{
    public class HighDiveAdminEngine : IHighDiveAdminEngine
    {
        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public event DelegateVoidDiveScoreDive EventUpdateJudgeScore;
        public event DelegateVoidContest EventUpdateDiveQueue;
        public event DelegateVoidJudge EventJudgeConnected;
        public event DelegateVoidContest EventContestCompleted;

        public bool ConnectionPhase { get; set; }

        #region Properties
        public Contest Contest { get; private set; }
        public PersonList ActiveViewingPersonList { get; set; }

        private readonly IsAliveChecker _aliveChecker;
        private Thread _aliveCheckerThread;
        private Thread _slowReplyCheckerThread;

        public Diver ActiveViewingDiver { get; set; }
        public Judge ActiveViewingJudge { get; set; }
        public Person ActiveViewingPerson { get; set; }
        public Dive ActiveViewingDive { get; set; }
        public DiveList ActiveViewingDiveList { get; set; }
        

        #endregion

        #region Constructors
        public HighDiveAdminEngine()
        {
            //TcpHandler.Instance.IpAddress = Console.ReadLine();
            ConnectionPhase = true;
            _aliveChecker = new IsAliveChecker(); 
            if (TcpHandler.Instance == null)
                throw new Exception();
            //subscribe to events
            TcpHandler.EventJudgeLoginAttempt += _judgeLoginAttempt;
            TcpHandler.EventAdminDiveScoreReceived += UpdateDiveScore;
            TcpHandler.EventConnectionFailed += _judgeConnectionFailed;
            TcpHandler.EventJudgeIsAlive += _aliveChecker.AliveMessageReceived;
            TcpHandler.EventJudgeLogout += _judgeAppLogout;
            IsAliveChecker.EventJudgeDisconnected += _judgeNoLongerAlive;
            SlowReplyChecker.EventSlowReply += _judgeSlowReply;
            _slowReplyCheckerThread = new Thread(SlowReplyChecker.SlowReplyCheckerThread);
        }

        private void _judgeSlowReply()
        {

            foreach (var judge in Contest.JudgeList)
            {
                if (!Contest.DiveQueue[0].DiveScoreList.Exists(x => x.Judge.Equals(judge)))
                {
                    lock (judge.Status)
                    {
                        if (judge.Status.Equals(JudgeStatus.Status.Connected))
                            judge.Status.SetTo(JudgeStatus.Status.SlowReply);
                    }
                    if (EventJudgeConnected != null)
                        EventJudgeConnected(judge);
                } 
            }

        }

        private void _judgeConnectionFailed(string ip)
        {
            Judge judge = Contest.JudgeList.Find(x => x.IpAdress == ip);
            lock (judge.Status)
            {
                judge.Status.SetTo(JudgeStatus.Status.Disconnected);
            }
            if (EventJudgeConnected != null)
                EventJudgeConnected(judge);
        }

        private void _judgeAppLogout(string idString)
        {
            Judge judge = Contest.JudgeList.Find(x => x.IdString == idString);
            lock (judge.Status)
            {
                judge.Status.SetTo(JudgeStatus.Status.Disconnected);
            }
            if (EventJudgeConnected != null)
                EventJudgeConnected(judge);
        }

        private void _judgeNoLongerAlive(string idString)
        {
            Judge judge = Contest.JudgeList.Find(x => x.IdString == idString);
            if (EventJudgeConnected != null)
                EventJudgeConnected(judge);
        }

        #endregion

        #region Public Methods
        /// <summary>
        /// Loads a contest from database
        /// </summary>
        /// <param name="id">The Contest's DbId</param>
        public Contest LoadContest(int? id)
        {
            try
            {
                Contest = DbHandler.Instance.LoadContestFromDb(id);
            }
            catch (Exception)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Contest with DbId (" + id + ") does not exist");
                throw new HighDiveEngineException("Loading contest with DbId (" + id + "). Contest does not exist!");
            }
            ConnectionPhase = true;
            return Contest;
        }

        public Person LoadPerson(string idnumber)
        {
            return DbHandler.Instance.LoadPerson(idnumber);
        }
        /// <summary>
        /// Resets the currently loaded contest
        /// </summary>
        public void ResetContest()
        {
            try
            {
                Contest = DbHandler.Instance.ResetContest(Contest.DbId);
            }
            catch (DbHandlerException)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Contest with DbId (" + Contest.DbId + ") does not exist");
                throw new HighDiveEngineException("Loading contest with DbId (" + Contest.DbId + "). Contest does not exist!");
            }
        }

        /// <summary>
        /// Creates a new Contest (unloading the current one)
        /// </summary>
        public Contest NewContest()
        {
            Contest = new Contest(){JudgeList = new JudgeList(), DiveHistory = new DiveList(), DiveQueue = new DiveList()};
            return Contest;
        }

        public void UpdateContestInformation(ContestArgs contestArgs)
        {
            Contest.Name = contestArgs.Name;
            Contest.Club = contestArgs.Club;
            Contest.Date = contestArgs.Date.ToShortDateString() + "-" + contestArgs.Date.Hour + contestArgs.Date.Minute;
            Contest.City = contestArgs.City;
            Contest.Country = contestArgs.Country;
        }


        /// <summary>
        /// Saves the complete currently loaded contest to DB.
        /// Note: This includes DiveLists, Dives, JudgeLists etc.
        /// </summary>
        public void SaveCompleteContest()
        {
            DbHandler.Instance.SaveCompleteContestToDb(Contest);
        }

        /// <summary>
        /// Adds/updates a DiveScore in the currently loaded contest
        /// </summary>
        /// <param name="dive"></param>
        /// <param name="score">The DiveScore to add/update</param>
        public void UpdateDiveScore(DiveScore score, Dive dive)
        {
            if (score.Judge.IdNumber == null)
            {
                if (Contest.DiveQueue.Count > 0)
                {
                    if (dive.Equals(Contest.DiveQueue[0]))
                        dive = Contest.DiveQueue[0];
                    else
                        dive = Contest.DiveHistory.Find(x => x.Equals(dive));
                }
                else
                    dive = Contest.DiveHistory.Find(x => x.Equals(dive));
                score.Judge = Contest.JudgeList.Find(x => x.Equals(score.Judge));

                _aliveChecker.UpdateLastHeardFrom(score.Judge.IdString);
                Contest.UpdateDiveScore(score, dive);
            }
            else
            {
                Contest.UpdateDiveScore(score, dive);
                SendManualScoreToJudge(score.Judge, dive);
            }

            DbHandler.Instance.SaveDiveScoreToDb(score, dive);

            var updateJudgeStatus = false;
            lock (score.Judge.Status)
            {
                if (score.Judge.Status.Equals(JudgeStatus.Status.SlowReply))
                {
                    score.Judge.Status.SetTo(JudgeStatus.Status.Connected);
                    updateJudgeStatus = true;
                }
            }
            if (updateJudgeStatus && EventJudgeConnected != null)
                EventJudgeConnected(score.Judge);

            if (dive.DiveScoreList.Count == 1)
            {
                _slowReplyCheckerThread = new Thread(SlowReplyChecker.SlowReplyCheckerThread);
                _slowReplyCheckerThread.Start();
            }
            else if (dive.DiveScoreList.Count == Contest.JudgeList.NumberOfJudges)
            {
                if (_slowReplyCheckerThread.IsAlive)
                    _slowReplyCheckerThread.Abort();
            }

            if (EventUpdateJudgeScore != null)
                EventUpdateJudgeScore(score, dive);

            //If all DiveScores have been entered, calculate total score
            if (Contest.CheckScoreComplete(dive))
            {
                dive.CalculateTotalScore();
                Thread.Sleep(1500);
                
                //if dive is the current active dive, call UpdateDiveQueue()
                if (dive.Equals(Contest.DiveQueue[0]))
                {
                    Contest.UpdateDiveQueue();
                    SendActiveDiveToJudges();
                    if (Contest.DiveQueue.Count == 0)
                    {
                        if (EventContestCompleted != null)
                            EventContestCompleted(Contest);
                    }
                    else
                    {
                        if (EventUpdateDiveQueue != null)
                            EventUpdateDiveQueue(Contest);
                    }
                }
            }
        }

        public void SendManualScoreToJudge(Judge judge, Dive dive)
        {
            lock (judge.Status)
            {
                if (judge.Status.Equals(JudgeStatus.Status.Connected) || judge.Status.Equals(JudgeStatus.Status.SlowReply))
                    TcpHandler.Instance.SendUpdatedScoreToJudge(judge, dive); 
            }
        }

        /// <summary>
        /// Loads a list of all contests from DB
        /// </summary>
        /// <returns>A list of all contests</returns>
        public ContestList LoadContestList(bool onlyUpcoming, bool onlyUnstarted)
        {
            return DbHandler.Instance.LoadContestList(onlyUpcoming, onlyUnstarted);
        }

        /// <summary>
        /// Change status on the currently loaded contest
        /// </summary>
        //public void SetContestStatus(ContestStatus status)
        //{
        //    Contest.Status = status;
        //    if (Contest.DbId != null)
        //        DbHandler.Instance.SetContestStatus(status, Contest.DbId);
        //}

        public void StartContest(bool offlineMode)
        {
            //Set contest status to started
            Contest.Status = ContestStatus.Started;

            if (!offlineMode)
            {
                //Start thread to control judge status
                _aliveChecker.LoadJudges(Contest.JudgeList);
                _aliveCheckerThread = new Thread(_aliveChecker.IsAliveThread) {IsBackground = true};
                _aliveCheckerThread.Start();
            }

            ////subscribe to events
            //TcpHandler.EventJudgeLoginAttempt += _judgeLoginAttempt;
            //TcpHandler.EventAdminDiveScoreReceived += UpdateDiveScore;
            //TcpHandler.EventConnectionFailed += _judgeConnectionFailed;
            //TcpHandler.EventJudgeHeardFrom += _aliveChecker.UpdateLastHeardFrom;
            //TcpHandler.EventJudgeIsAlive += _aliveChecker.AliveMessageReceived;

            //update contest status in db
            if (Contest.DbId != null)
                DbHandler.Instance.SetContestStatus(Contest.Status, Contest.DbId);
        }

        public void EndContest()
        {
            //Set contest status to finished
            Contest.Status = ContestStatus.Finished;

            //end alive checker thread
            if (_aliveCheckerThread != null)
            {
                if (_aliveCheckerThread.IsAlive)
                    _aliveCheckerThread.Abort(); 
            }

            foreach (var judge in Contest.JudgeList)
            {
                lock (judge.Status)
                {
                    if (judge.Status.Equals(JudgeStatus.Status.Connected) || judge.Status.Equals(JudgeStatus.Status.SlowReply))
                        TcpHandler.Instance.SendContestEnded(judge);
                }
            } 


            //save contest to db
            if (Contest.DbId != null)
                DbHandler.Instance.SaveCompleteContestToDb(Contest);
        }

        /// <summary>
        /// 
        /// </summary>
        public void SendActiveDiveToJudges()
        {
            if (Contest.DiveQueue.Count != 0)
                TcpHandler.Instance.SendNewDiveToJudge(Contest.JudgeList, Contest.DiveQueue[0]);
        }

        /// <summary>
        /// 
        /// </summary>
        public void SendActiveDiveToJudge(string idString)
        {
            if (Contest.DiveQueue.Count != 0)
                TcpHandler.Instance.SendNewDiveToJudge(new JudgeList { Contest.JudgeList.Find(x => x.IdString == idString) }, Contest.DiveQueue[0]);
        }

        /// <summary>
        /// 
        /// </summary>
        //public void SendEditedDiveToJudges(Dive dive)
        //{
        //    TcpHandler.Instance.SendUpdatedScoreToJudge(Contest.JudgeList, dive);
        //}

        /// <summary>
        /// 
        /// </summary>
        public void SaveContestData()
        {
            DbHandler.Instance.SaveContestDataToDb(Contest);
        }

        /// <summary>
        /// Adds a dive to the currently active contest
        /// </summary>
        /// <param name="dive"></param>
        public void AddDive(Dive dive)
        {
            if (Contest == null)
                throw new HighDiveEngineException("No contest set!");

            try
            {
                //DbHandler.Instance.SaveDiveToDb(dive, Contest.DbId);
            }
            catch (DbHandlerException)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Error adding dive");
                throw new HighDiveEngineException("Error adding dive");
            }
            
            if (dive.DiveScoreList.Count == Contest.JudgeList.NumberOfJudges)
                Contest.DiveHistory.Add(dive);
            else
                Contest.DiveQueue.Add(dive);
            
        }

        public void RemoveDive(Dive dive)
        {
            var result = MessageBox.Show("Removing a dive is permanent, press yes to continue.", "Remove Dive", MessageBoxButtons.YesNo);
            if (result == DialogResult.Yes)
            {
            if (Contest == null)
                throw new HighDiveEngineException("No contest set!");

            try
            {
                DbHandler.Instance.DeleteDive(dive);
            }
            catch (DbHandlerException)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Error removing dive");
                throw new HighDiveEngineException("Error removing dive");
            }


            if (dive.DiveScoreList.Count == Contest.JudgeList.NumberOfJudges)
                Contest.DiveHistory.Remove(dive);
            else
                Contest.DiveQueue.Remove(dive);
        }
        }

        public void AddJudge(Judge judge)
        {
            try
            {
                Contest.JudgeList.Add(judge);
            }
            catch (JudgeListException)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Cannot add judge to list");
                throw new HighDiveEngineException("Cannot add judge to list, is the list full?");
            }
            if (Contest.JudgeList.Count == Contest.JudgeList.NumberOfJudges)
                Contest.JudgeList.GenerateIdStrings();
        }

        public void RemoveJudge(Judge judge)
        {
            try
            {
                Contest.JudgeList.Remove(judge);
            }
            catch (JudgeListException)
            {
                if (Log.IsErrorEnabled)
                    Log.Error("Cannot remove judge from list!");
                throw new HighDiveEngineException("Cannot remove judge from list!");
            }
        }

        public void SetNumJudges(int numJudges)
        {
            if (numJudges < Contest.JudgeList.Count)
            {
                var result = MessageBox.Show("Lowering number of judges will cause some judges to be permanently deleted, press yes to continue.", "Remove Judges", MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes)
                {
                    var jlist = Contest.JudgeList.FindAll(x => x.JudgeNumber > numJudges);
                    foreach (var judge in jlist)
                    {
                        RemoveJudge(judge);
                        if(judge.DbId != null)
                        DbHandler.Instance.DeleteJudge(judge);
                    }
                    Contest.JudgeList.NumberOfJudges = numJudges;
                }
            }
            else
                Contest.JudgeList.NumberOfJudges = numJudges;
        }    

        public void LoadPersonList()
        {
            ActiveViewingPersonList = DbHandler.Instance.LoadPersonListFromDb();
        }

        public Dive GetActveDive()
        {
            return Contest.DiveQueue[0];
        }

        public Judge GetJudge(int judgeNumber)
        {
            if (judgeNumber > Contest.JudgeList.NumberOfJudges)
                throw new HighDiveEngineException("invalid judge number");
            return Contest.JudgeList.Find(x => x.JudgeNumber == judgeNumber);
        }
    
        #endregion

        #region Private Methods

        private void _judgeLoginAttempt(string ipAdress, string idString)
        {
            Judge judge = Contest.JudgeList.Find(x => x.IdString == idString);
            if (judge != null)
            {
                judge.IpAdress = ipAdress;
                lock (judge.Status)
                {
                    if (judge.Status.Equals(JudgeStatus.Status.Connected) || judge.Status.Equals(JudgeStatus.Status.SlowReply))
                    {
                        TcpHandler.Instance.SendLoginRefusedToIp(ipAdress);
                        return;
                    }
                    judge.Status.SetTo(JudgeStatus.Status.Connected);
                }
                TcpHandler.Instance.SendLoginSuccessfulToJudge(judge);
                if (EventJudgeConnected != null) 
                    EventJudgeConnected(judge);
                if (Contest.Status == ContestStatus.Started)
                    SendActiveDiveToJudge(idString);
            }
            else
            {
                TcpHandler.Instance.SendLoginRefusedToIp(ipAdress);
            }
        }

        #endregion


        public void NewPerson(Judge judge)
        {
            DbHandler.Instance._savePersonToDb(judge);
        }

        public void updateJudgeList(Judge judge)
        {
            Judge j = Contest.JudgeList.Find(x => x.JudgeNumber == judge.JudgeNumber);
            if (j != null)
                j = judge;
            else
                Contest.JudgeList.Add(judge);
        }


        public void SetActiveViewingJudge(Judge judge)
        {
            ActiveViewingJudge = judge;
        }

        public void SetActiveViewingDiver(Diver diver)
        {
            ActiveViewingDiver = diver;
        }

        public void SetActiveViewingDiveList(DiveList diveList)
        {
            ActiveViewingDiveList = diveList;
        }


        public Judge LoadJudge(string id)
        {
            return DbHandler.Instance.LoadJudge(id);
        }


        public void LoadActiveViewingDive(int? id)
        {
            ActiveViewingDive = DbHandler.Instance.LoadDiveFromDb(id);
        }

        public void SaveActiveViewingDive(Dive dive)
        {
            if (dive.DiveCode != null)
            {
                ActiveViewingDive.DiveCode = dive.DiveCode;
                ActiveViewingDive.CalculateDifficulty();
            }
            if (dive.DiveHeight != null)
                ActiveViewingDive.DiveHeight = dive.DiveHeight;
            if (dive.DiveNumber != 0)
                ActiveViewingDive.DiveNumber = dive.DiveNumber;
            if (dive.QueueOrderNumber != null)
                ActiveViewingDive.QueueOrderNumber = dive.QueueOrderNumber;
            if (dive.DiveScoreList.Count != 0)
            {
                ActiveViewingDive.DiveScoreList.Clear();
                ActiveViewingDive.DiveScoreList.AddRange(dive.DiveScoreList);
                ActiveViewingDive.CalculateTotalScore();
            }
            DbHandler.Instance.SaveDiveToDb(ActiveViewingDive, Contest.DbId);

            var index = Contest.DiveQueue.FindIndex(x => x.Equals(ActiveViewingDive));
            if (index != -1)
            {
                Contest.DiveQueue[index] = ActiveViewingDive;
                if (index == 0)
                    SendActiveDiveToJudges();
            }

            index = Contest.DiveHistory.FindIndex(x => x.Equals(ActiveViewingDive));
            if (index != -1)
            {
                Contest.DiveHistory[index] = ActiveViewingDive;
                foreach (var judge in Contest.JudgeList)
                {
                    TcpHandler.Instance.SendUpdatedScoreToJudge(judge, Contest.DiveHistory[index]); 
                }
            }
            ActiveViewingDive = null;
        }

        public string GetAdminIp()
        {
            return TcpHandler.Instance.IpAddress.Split(':')[0];
        }

        public void RemoveDiver(Diver diver)
        {
            var result = MessageBox.Show("Removing a diver is permanent, press yes to continue.", "Remove Diver", MessageBoxButtons.YesNo);
            if (result == DialogResult.Yes)
            {
                if (Contest == null)
                    throw new HighDiveEngineException("No contest set!");

                try
                {
                    if (diver.DbId != null)
                        DbHandler.Instance.DeleteDiver(diver);
                }
                catch (DbHandlerException)
                {
                    if (Log.IsErrorEnabled)
                        Log.Error("Error removing diver");
                    throw new HighDiveEngineException("Error removing diver");
                }
                Contest.DiveQueue.RemoveAll(x => x.Diver.Equals(diver));
            }
        }
    }
}
